﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.ADF.ArcGISServer;
using ESRI.ArcGIS.SOESupport;
using WorldViewSolutions.ArcGISServer.SOE.DataSources;

namespace WorldViewSolutions.ArcGISServer.SOE
{

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    public class ExportParameters
    {
        /// <summary>
        /// Gets or sets the color of the background.
        /// </summary>
        /// <value>The color of the background.</value>
        public System.Drawing.Color BackgroundColor { get; set; }

        /// <summary>
        /// Gets or sets the extent.
        /// </summary>
        /// <value>The extent.</value>
        public EnvelopeN Extent { get; set; }

        /// <summary>
        /// Gets or sets the COM spatial reference.
        /// </summary>
        /// <value>The COM spatial reference.</value>
        public ESRI.ArcGIS.Geometry.ISpatialReference COMSpatialReference { get; set; }

        /// <summary>
        /// Gets or sets the AGS spatial reference.
        /// </summary>
        /// <value>The AGS spatial reference.</value>
        public ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference AGSSpatialReference { get; set; }

        /// <summary>
        /// Gets or sets the scale.
        /// </summary>
        /// <value>The scale.</value>
        public double? Scale { get; set; }

        /// <summary>
        /// Gets or sets the format.
        /// </summary>
        /// <value>The format.</value>
        public ExportFormat Format { get; set; }

        /// <summary>
        /// Gets or sets the width.
        /// </summary>
        /// <value>The width.</value>
        public double Width { get; set; }

        /// <summary>
        /// Gets or sets the height.
        /// </summary>
        /// <value>The height.</value>
        public double Height { get; set; }

        /// <summary>
        /// Gets or sets the DPI.
        /// </summary>
        /// <value>The DPI.</value>
        public int DPI { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public List<ExportMapResource> Resources;

        /// <summary>
        /// 
        /// </summary>
        public List<WorldViewSolutions.Graphics.Graphic> Graphics;

        /// <summary>
        /// 
        /// </summary>
        public Dictionary<string, string> TextElements;

        /// <summary>
        /// 
        /// </summary>
        public Dictionary<string, object> Attributes;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Object"/> class.
        /// </summary>
        public ExportParameters()
        {
            Resources = new List<ExportMapResource>();
            Graphics = new List<WorldViewSolutions.Graphics.Graphic>();
            TextElements = new Dictionary<string, string>();
            Attributes = new Dictionary<string, object>();
            DPI = 96;
            Width = 4;
            Height = 4;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExportParameters"/> class.
        /// </summary>
        /// <param name="operationInput">The operation input.</param>
        /// <remarks></remarks>
        public ExportParameters(JsonObject operationInput)
            : this()
        {

            /********************************************************************
            *  Parameters
            ********************************************************************

               bbox: <xmin>, <ymin>, <xmax>, <ymax>
               bboxSR: {"wkid" : <wkid>} OR {"wkt" : "<wkt>"}
               scale: <scale>
               size: <width>, <height>
               dpi: <dpi>
               backgroundColor: <html color> - Example: #FFFFFF
               format: < pdf | jpg >
               resources:[    
                   {
                       "name":"/MERG/Test",
                       "type":"MapD.Data.AgsDynamicMapResource",
                       "opacity":80,
                       "dpi":96,
                       "parameters":{
                            "restUrl":"http://standev/ArcGIS/rest/services/MERG/Test/MapServer/",
                            "token":null,
                            "layers":[            
                                {
                                    "id":"0",
                                    "visible":true
                                },
                                {
                                    "id":"1",
                                    "visible":true
                                }
                            ]
                        }
                   },
                   {
                       "name":"VBMP 2007",
                       "type":"MapD.Data.DisconnectedMapResource",
                       "opacity":1,
                       "dpi":300,
                       "parameters":{
                           "serviceUrls":[
                               "http://gismaps.virginia.gov/arcgisserver1_COVDBICES-MCL08/arcgiscache/vbmp2006_2007/Layers/_alllayers"
                           ],
                           "configFile":"C:\\Sandbox\\MapsDirect\\Sites\\M3\\App_Data\\vbmp07_conf.xml",
                           "urlFormat":"disconnected"
                       }
                   }
               ],
               graphics:[],
               textElements[
                    [<name>,<value>]
               ]
             

            *********************************************************************/

            string bbox, size, backgroundColor, format;
            double? scale;
            long? dpi;

            // bbox/extent  (Required)
            if (!operationInput.TryGetString("bbox", out bbox))
                throw new ArgumentNullException("bbox is required");

            string[] bboxParts = bbox.Split(new char[] { ',' });
            if (bboxParts.Length != 4)
                throw new Exception("Invalid bbox");

            double xmin, ymin, xmax, ymax;
            if (!Double.TryParse(bboxParts[0], out xmin)
                || !Double.TryParse(bboxParts[1], out ymin)
                || !Double.TryParse(bboxParts[2], out xmax)
                || !Double.TryParse(bboxParts[3], out ymax))
                throw new Exception("Invalid bbox");




            // bboxSR/SpatialReference (Required)
            JsonObject bboxSR = null;
            if (!operationInput.TryGetJsonObject("bboxSR", out bboxSR))
                throw new ArgumentNullException("bboxSR is required");

            string wkt = null;
            long? wkid = null;

            if (!bboxSR.TryGetString("wkt", out wkt))
            {
                if (!bboxSR.TryGetAsLong("wkid", out wkid))
                    throw new Exception("Invalid bboxSR");
            }

            if (wkt != null)
            {
                COMSpatialReference = SpatialUtilities.CreateCOMSpatialReference(wkt);
                AGSSpatialReference = SpatialUtilities.CreateAGSSpatialReference(wkt);
            }
            else
            {
                COMSpatialReference = SpatialUtilities.CreateCOMSpatialReference((int)wkid.Value);
                AGSSpatialReference = SpatialUtilities.CreateAGSSpatialReference((int)wkid.Value);
            }

            Extent = new EnvelopeN();
            Extent.XMin = xmin;
            Extent.YMin = ymin;
            Extent.XMax = xmax;
            Extent.YMax = ymax;
            Extent.SpatialReference = AGSSpatialReference;

         
            if (operationInput.Exists("scale"))
            {
                if (operationInput.TryGetAsDouble("scale", out scale))
                {
                    Scale = scale;
                }
            }


            // size  (Optional) Only used for image exports, PDF templates use the map area defined in the MXD
            if (operationInput.Exists("size"))
            {
                operationInput.TryGetString("size", out size);
                if (!String.IsNullOrEmpty(size))
                {
                    string[] sizeParts = size.Split(new char[] { ',' });
                    if (sizeParts.Length != 2)
                        throw new Exception("Invalid size");

                    double width, height;
                    if (!Double.TryParse(sizeParts[0], out width) || !Double.TryParse(sizeParts[1], out height))
                        throw new ArgumentNullException("Invalid size");

                    Width = width;
                    Height = height;
                }
            }

            // backgroundColor (Optional)
            if (operationInput.TryGetString("backgroundColor", out backgroundColor))
            {
                try
                {
                    BackgroundColor = System.Drawing.ColorTranslator.FromHtml(backgroundColor);
                }
                catch
                {
                    BackgroundColor = System.Drawing.Color.White;
                }
            }

            // format (Optional)
            if (operationInput.TryGetString("format", out format))
            {
                try
                {
                    Format = (ExportFormat)Enum.Parse(Format.GetType(), format.ToUpper(), true);
                }
                catch
                {
                    Format = ExportFormat.JPG;
                }
            }

            // Resources (Required)
            Resources = GetExportMapResources(operationInput);

            // graphic/features (Optional)
            if (operationInput.Exists("graphics"))
            {
                object[] graphicsArray;
                if (operationInput.TryGetArray("graphics", out graphicsArray))
                {
                    foreach (JsonObject jo in graphicsArray)
                    {
                        WorldViewSolutions.Geometry.Geometry geometry = GeometryUtilities.FromRestGeometry(jo);
                        if (geometry != null)
                        {
                            WorldViewSolutions.Graphics.Graphic graphic = new WorldViewSolutions.Graphics.Graphic();

                            string name = null;
                            if (jo.Exists("name"))
                            {
                                jo.TryGetString("name", out name);
                            }
                            if (String.IsNullOrEmpty(name))
                            {
                                name = geometry.GetType().Name;
                            }
                            graphic.Name = name;
                            graphic.Style = GetStyle(jo);
                            graphic.Geometry = geometry;
                            Graphics.Add(graphic);
                        }
                    }
                }
            }

            // TextElements
            if (operationInput.Exists("textElements"))
            {
                JsonObject joTextElements;
                if (operationInput.TryGetJsonObject("textElements", out joTextElements))
                {
                    System.Collections.IEnumerator o = joTextElements.GetEnumerator();
                    while (o.MoveNext())
                    {
                        KeyValuePair<string, object> a = (KeyValuePair<string, object>)o.Current;
                        TextElements.Add(a.Key, a.Value.ToString());
                    }
                }
            }

            // Attributes
            if (operationInput.Exists("attributes"))
            {
                JsonObject joAttributes;
                if (operationInput.TryGetJsonObject("attributes", out joAttributes))
                {
                    System.Collections.IEnumerator o = joAttributes.GetEnumerator();
                    while (o.MoveNext())
                    {
                        KeyValuePair<string, object> a = (KeyValuePair<string, object>)o.Current;
                        Attributes.Add(a.Key, a.Value);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the style.
        /// </summary>
        /// <param name="jo">The jo.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private WorldViewSolutions.Graphics.Style GetStyle(JsonObject jo)
        {
            WorldViewSolutions.Graphics.Style style = new WorldViewSolutions.Graphics.Style();

            if (!jo.Exists("style")) return style;

            JsonObject joStyle;
            if (!jo.TryGetJsonObject("style", out joStyle)) return style;

            if (joStyle.Exists("lineType"))
            {
                string lineType;
                if (joStyle.TryGetString("lineType", out lineType))
                    style.LineType = lineType;
            }

            if (joStyle.Exists("lineColor"))
            {
                string lineColor;
                if (joStyle.TryGetString("lineColor", out lineColor))
                    style.LineColor = lineColor;
            }

            if (joStyle.Exists("lineWidth"))
            {
                long? lineWidth;
                if (joStyle.TryGetAsLong("lineWidth", out lineWidth))
                    style.LineWidth = (int)lineWidth.Value;
            }

            if (joStyle.Exists("lineOpacity"))
            {
                double? lineOpacity;
                if (joStyle.TryGetAsDouble("lineOpacity", out lineOpacity))
                    style.LineOpacity = lineOpacity.Value;
            }

            if (joStyle.Exists("fillColor"))
            {
                string fillColor;
                if (joStyle.TryGetString("fillColor", out fillColor))
                    style.FillColor = fillColor;
            }

            if (joStyle.Exists("fillOpacity"))
            {
                double? fillOpacity;
                if (joStyle.TryGetAsDouble("fillOpacity", out fillOpacity))
                    style.FillOpacity = fillOpacity.Value;
            }

            if (joStyle.Exists("textSize"))
            {
                long? textSize;
                if (joStyle.TryGetAsLong("textSize", out textSize))
                    style.TextSize = (int)textSize.Value;
            }

            if (joStyle.Exists("iconStyle"))
            {
                JsonObject joIconStyle;
                if (joStyle.TryGetJsonObject("iconStyle", out joIconStyle))
                {
                    WorldViewSolutions.Graphics.IconStyle iconStyle = new WorldViewSolutions.Graphics.IconStyle();

                    if (joIconStyle.Exists("url"))
                    {
                        string url;
                        if (joIconStyle.TryGetString("url", out url))
                            iconStyle.Url = url;
                    }

                    if (joIconStyle.Exists("offsetX"))
                    {
                        long? offsetX;
                        if (joIconStyle.TryGetAsLong("offsetX", out offsetX))
                            iconStyle.OffsetX = (int)offsetX.Value;
                    }

                    if (joIconStyle.Exists("offsetY"))
                    {
                        long? offsetY;
                        if (joIconStyle.TryGetAsLong("offsetY", out offsetY))
                            iconStyle.OffsetY = (int)offsetY.Value;
                    }


                    style.IconStyle = iconStyle;
                }
            }

            return style;
        }

        /// <summary>
        /// Gets the export map resources.
        /// </summary>
        /// <param name="jsonResources">The json resources.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private List<ExportMapResource> GetExportMapResources(JsonObject jsonResources)
        {
            List<ExportMapResource> mapResources = new List<ExportMapResource>();

            if (!jsonResources.Exists("resources"))
                throw new NullReferenceException("resources is required");

            object[] resourceArray;
            if (!jsonResources.TryGetArray("resources", out resourceArray))
            {
                object ro;
                jsonResources.TryGetObject("resources", out ro);
                throw new Exception("Invalid resources: type = " + ro.GetType() + ", value = " + ro.ToString());
            }

            foreach (JsonObject jo in resourceArray)
            {
                // create an instance of the resource type
                WorldViewSolutions.ArcGISServer.SOE.DataSources.IMapDResource resource = null;
                string type = JsonHelper.ParseString(jo, "type");
                string name = JsonHelper.ParseString(jo, "name");
                double opacity = JsonHelper.ParseDouble(jo, "opacity", 1.0);
                List<DynamicLayer> dynamicLayers = null;

                ExportImageQuality exportImageQuality = ExportImageQuality.Standard;
                if (jo.Exists("quality"))
                {
                    string quality;
                    if (jo.TryGetString("quality", out quality))
                    {
                        try
                        {
                            exportImageQuality = (ExportImageQuality)Enum.Parse(typeof(ExportImageQuality), quality, true);
                        }
                        catch { }
                    }
                }

                Uri uri = null;

                switch (type)
                {
                    case "BingTiledMapService":
                        ArcGISLog.Write("BingTiledMapResource");
                        string bingMapsKey = JsonHelper.ParseString(jo, "bingMapsKey");
                        string culture = JsonHelper.ParseString(jo, "culture");
                        string imagerySet = JsonHelper.ParseString(jo, "imagerySet");
                        List<string> bingUrls = new List<string>();
                        bingUrls.Add("http://t0.tiles.virtualearth.net/tiles/r");

                        resource = new WorldViewSolutions.ArcGISServer.SOE.DataSources.BingTiledMapResource(null, bingUrls.ToArray(), bingMapsKey, culture, imagerySet);
                        break;
                    case "ArcGISTiledMapService":
                        string restUrl = JsonHelper.ParseString(jo, "restUrl");

                        System.Collections.Specialized.NameValueCollection query = System.Web.HttpUtility.ParseQueryString(restUrl);
                        if (query["token"] == null)
                        {
                            uri = new Uri(restUrl);
                            string serviceUrl = uri.GetLeftPart(UriPartial.Path);

                            if (ServiceCache.Items.ContainsKey(serviceUrl))
                            {
                                resource = ServiceCache.Items[serviceUrl];
                            }
                            else
                            {
                                resource = new AgsTiledMapResource(null, restUrl);
                                ServiceCache.Items[serviceUrl] = resource;
                            }
                        }
                        else
                        {
                            resource = new AgsTiledMapResource(null, restUrl);
                        }
                        break;
                    case "ArcGISDynamicMapService":
                        string url = JsonHelper.ParseString(jo, "restUrl");
                        string token = JsonHelper.ParseString(jo, "token");
                        string soapUrl = url.Replace("/rest/", "/");

                        if (!String.IsNullOrEmpty(token)) soapUrl += "?token=" + token;

                        AgsMapResource agsResource = new AgsMapResource(soapUrl);

                        object[] layersArray;
                        jo.TryGetArray("layers", out layersArray);

                        dynamicLayers = new List<DynamicLayer>();

                        foreach (JsonObject jsonLayer in layersArray)
                        {
                            long? layerId;
                            bool? visible;
                            bool? displayInTOC;

                            // if there is no id the skip
                            if (!jsonLayer.TryGetAsLong("id", out layerId))
                                continue;

                            if (!jsonLayer.TryGetAsBoolean("visible", out visible))
                                continue;

                            if (!jsonLayer.TryGetAsBoolean("displayInTOC", out displayInTOC))
                                displayInTOC = true;

                            foreach (LayerDescription ld in agsResource.MapDescription.LayerDescriptions)
                            {
                                if (ld.LayerID == layerId.Value)
                                {
                                    //ArcGISLog.Write("Setting layer visibility: " + layerId.Value + " (" + ld.Visible + ") = " + visible.Value);
                                    ld.Visible = visible.Value;

                                    dynamicLayers.Add(new DynamicLayer() { ID = (int)layerId.Value, Visible = visible.Value, DisplayInTOC = displayInTOC.Value });
                                    break;
                                }
                            }
                        }

                        resource = agsResource;
                        break;
                }

                mapResources.Add(new ExportMapResource(name, resource, exportImageQuality, opacity, true, dynamicLayers));
            }

            return mapResources;
        }

        /// <summary>
        /// Fixes the exent.
        /// </summary>
        /// <remarks></remarks>
        public void FixExent()
        {

            if (Scale.HasValue)
            {
                //ArcGISLog.Write("FixExent - input extent: - xmin = " + Extent.XMin + ", ymin " + Extent.YMin + ", xmax = " + Extent.XMax + ", ymax = " + Extent.YMax);
                Extent = SpatialUtilities.GetEnvelopeAtScale(Scale.Value, Extent, DPI, Convert.ToInt32(Width * DPI), Convert.ToInt32(Height * DPI));
                //ArcGISLog.Write("FixExent - output extent: - xmin = " + Extent.XMin + ", ymin " + Extent.YMin + ", xmax = " + Extent.XMax + ", ymax = " + Extent.YMax);
            }
            else
            {

                /**
                * Adjust the width and/or height of the extent to match the aspect ratio of the givin map. The resulting extent will always fully contain the original extent.
                * @method
                * @param {MapD.Controls.Map} map Map instance to match the ratio to. 
                */

                double extentWidth = Extent.XMax - Extent.XMin;
                double extentHeight = Extent.YMax - Extent.YMin;

                double mapAspectRatio = Width / Height;
                double mapAspectRatioReversed = Height / Width;
                double extentAspectRatio = extentWidth / extentHeight;
                double extentAspectRatioReversed = extentHeight / extentWidth;
                bool adjustHeight = true;

                if (extentAspectRatio < mapAspectRatio) adjustHeight = false;

                //adjust height
                if (adjustHeight)
                {
                    double newHeight = extentHeight / extentAspectRatioReversed * mapAspectRatioReversed;
                    double difference = newHeight - extentHeight;
                    Extent.YMin -= difference / 2;
                    Extent.YMax += difference / 2;
                    //adjust width
                }
                else
                {
                    double newWidth = extentWidth / extentAspectRatio * mapAspectRatio;
                    double difference = newWidth - extentWidth;
                    Extent.XMin -= difference / 2;
                    Extent.XMax += difference / 2;
                }
            }
        }
    }
}
